package com.cmc.cloud.cmclink.doc.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cmc.cloud.cmclink.doc.api.document.emums.*;
import com.cmc.cloud.cmclink.doc.constants.RedisKeyConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.SoErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.SpecialCargoApplyErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.SpecialCargoCntrErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.convert.*;
import com.cmc.cloud.cmclink.doc.entity.*;
import com.cmc.cloud.cmclink.doc.enums.filter.SpecialValidatorEnum;
import com.cmc.cloud.cmclink.doc.mapper.*;
import com.cmc.cloud.cmclink.doc.service.ApplySaveOrUpdateService;
import com.cmc.cloud.cmclink.doc.service.SpecialCargoApplyService;
import com.cmc.cloud.cmclink.doc.service.impl.so.AttachmentServiceImpl;
import com.cmc.cloud.cmclink.doc.service.impl.so.SoForecastServiceImpl;
import com.cmc.cloud.cmclink.doc.service.so.RequirementsService;
import com.cmc.cloud.cmclink.doc.service.special.EmailTaskService;
import com.cmc.cloud.cmclink.doc.service.special.SpcExternalApiService;
import com.cmc.cloud.cmclink.doc.service.special.SpecialCargoValidateService;
import com.cmc.cloud.cmclink.doc.util.CostChargeUtil;
import com.cmc.cloud.cmclink.doc.util.RedisUtils;
import com.cmc.cloud.cmclink.doc.util.VslvoyUtil;
import com.cmc.cloud.cmclink.doc.util.sepcial.CommonUtil;
import com.cmc.cloud.cmclink.doc.vo.sovo.attachmentvo.AttachmentCountVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.attachmentvo.AttachmentRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.sovslvoyvo.SoVslvoyRespVO;
import com.cmc.cloud.cmclink.doc.vo.special.externalapivo.*;
import com.cmc.cloud.cmclink.doc.vo.specialcargoapplyvo.*;
import com.cmc.cloud.cmclink.doc.vo.specialcargocntrvo.SpecialCargoCntrBaseVO;
import com.cmc.cloud.cmclink.doc.vo.specialcargocntrvo.SpecialCargoCntrRespVO;
import com.cmc.cloud.cmclink.doc.vo.specialcargodgpackingvo.PackingCreateOrUpdateVO;
import com.cmc.cloud.cmclink.doc.vo.specialcargodgvo.DgByBookingNo;
import com.cmc.cloud.cmclink.doc.vo.specialcargodgvo.SpecialCargoDgBaseVO;
import com.cmc.cloud.cmclink.doc.vo.specialcargoflexivo.SpecialCargoFlexiRespVO;
import com.cmc.cloud.cmclink.doc.vo.specialcargoodovo.SpecialCargoOdoRespVO;
import com.cmc.cloud.cmclink.doc.vo.specialcargooogvo.SpecialCargoOogRespVO;
import com.cmc.cloud.cmclink.system.api.api.dict.DictDataApi;
import com.cmc.cloud.cmclink.system.api.api.dict.dto.DictDataRespDTO;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.common.pojo.CommonResult;
import com.cmes.framework.common.pojo.PageResult;
import com.cmes.framework.ex.Assertion;
import com.cmes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cmes.framework.security.core.util.SecurityFrameworkUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.Year;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 特种货审批主信息 Service 实现类
 *
 * @author 集运管理员
 */
@Service
@Validated
public class SpecialCargoApplyServiceImpl implements SpecialCargoApplyService {

    private static final int DICT_STATUS = 1;

    @Resource
    private SpecialCargoApplyMapper specialCargoApplyMapper;
    @Resource
    private SpecialCargoVoyageMapper specialCargoVoyageMapper;
    @Resource
    private SpecialCargoCntrMapper specialCargoCntrMapper;
    @Resource
    private SpecialCargoOogMapper specialCargoOogMapper;
    @Resource
    private SpecialCargoOdoMapper specialCargoOdoMapper;
    @Resource
    private SpecialCargoDgMapper specialCargoDgMapper;
    @Resource
    private SpecialCargoFlexiMapper specialCargoFlexiMapper;
    @Resource
    private SpecialCargoDgPackingMapper specialCargoDgPackingMapper;
    @Resource
    private SpecialCargoCasMapper specialCargoCasMapper;
    @Resource
    private SoMapper soMapper;
    @Resource
    private SoVslvoyMapper soVslvoyMapper;
    @Resource
    private AttachmentServiceImpl attachmentService;
    @Resource
    private VesselOffHireMaintenanceMapper vesselOffHireMaintenanceMapper;
    @Resource
    private ContainerMaxPayloadMapper containerMaxPayloadMapper;
    @Resource
    private AttachmentTypeMapper attachmentTypeMapper;
    @Resource
    private AttachmentMapper attachmentMapper;
    @Resource
    private RedisUtils redisUtils;
    @Resource
    private SoForecastServiceImpl soForecastService;
    @Resource
    private SpcExternalApiService spcExternalApiService;
    @Resource
    private CargoCntrTypeRelationMapper cargoCntrTypeRelationMapper;
    @Resource
    private SpecialCargoDgExtMapper specialCargoDgExtMapper;
    @Resource
    private EmailTaskService emailTaskService;
    @Resource
    private CostChargeUtil costChargeUtil;
    @Resource
    private VslvoyUtil vslvoyUtil;
    @Resource
    private DictDataApi dictDataApi;
    @Resource
    private ApplySaveOrUpdateService applySaveOrUpdateService;
    @Resource
    private RequirementsService requirementsService;


    public List<SpecialCargoCntrBaseVO> getCntrBySoBooking(Long soId) {
        List<SpecialCargoCntrBaseVO> cntrList = new ArrayList<>();
        if (soId != null) {
            List<SoForecastDO> soForecastDos = soForecastService.seleteBySoId(soId);
            if (CollectionUtil.isNotEmpty(soForecastDos)) {
                soForecastDos.forEach(soForecastDO -> {
                    SpecialCargoCntrBaseVO cntrBaseVO = new SpecialCargoCntrBaseVO();
                    cntrBaseVO.setCntrType(soForecastDO.getCntrType());
                    cntrBaseVO.setCntrSize(soForecastDO.getCntrSize());
                    cntrBaseVO.setQty(Math.toIntExact(soForecastDO.getCntrQuantity()));
                    cntrBaseVO.setGrossWgt(null);
                    cntrBaseVO.setNetWgt(null);
                    cntrList.add(cntrBaseVO);
                });
            }
        }
        return cntrList;
    }

    public List<String> getRequirementsBySoBooking(Long soId) {
        List<RequirementsDO> requirementsSelected = requirementsService.getRequirementsSelected(soId);
        return requirementsSelected.stream().map(RequirementsDO::getRequireCode).collect(Collectors.toList());
    }

    @Override
    public VoyageRespVO getSpecialCargoApplyVoyage(SpecialCargoVoyageReqVO reqVO) {
        VoyageRespVO voyageInfo = new VoyageRespVO();
        if (StringUtils.isNotEmpty(reqVO.getBookingNo())) {
            LambdaQueryWrapperX<SpecialCargoApplyDO> specialCargoApplyWrapperX = new LambdaQueryWrapperX<>();
            specialCargoApplyWrapperX.eq(SpecialCargoApplyDO::getBookingNo, reqVO.getBookingNo())
                    .ne(SpecialCargoApplyDO::getApplyStatus, ApplyStatusEnum.APPLY_CANCELLED.getValue())
                    .orderByDesc(SpecialCargoApplyDO::getCreateTime).last("LIMIT 1");
            SpecialCargoApplyDO specialCargoApplyDo = specialCargoApplyMapper.selectOne(specialCargoApplyWrapperX);
            if (null != specialCargoApplyDo) {
                throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.APPLY_SO_BOOKING_USED);
            }
            LambdaQueryWrapperX<SoDO> queryWrapperX = new LambdaQueryWrapperX<SoDO>().eq(SoDO::getBookingNo, reqVO.getBookingNo()).eq(SoDO::getValidStatus, ValidStatusEnum.DEFAULT_VALID.getValue()).last("LIMIT 1");
            SoDO soDo = soMapper.selectOne(queryWrapperX);
            if (null == soDo) {
                throw ServiceExceptionUtil.exception(SoErrorCodeConstants.SO_NOT_EXISTS);
            }
            if (SoStatusEnum.BookingRejected.getValue().equals(soDo.getSoStatus())) {
                throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.CREATE_SO_REJECT_ERROR);
            }
            if (SoStatusEnum.BookingCancelledByCustomer.getValue().equals(soDo.getSoStatus())) {
                throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.CREATE_SO_CANCEL_ERROR);
            }
            List<SoVslvoyDO> soVslvoyList = soVslvoyMapper.selectList(new LambdaQueryWrapper<SoVslvoyDO>().eq(SoVslvoyDO::getSoId, soDo.getId()).orderByAsc(SoVslvoyDO::getRouteNo));
            if (CollectionUtil.isNotEmpty(soVslvoyList)) {
                List<SpecialCargoVoyageUpdateReqVO> voyages = getSpecialCargoVoyageDo(soVslvoyList);
                costChargeUtil.setVslvoyTerminal(voyages);
                vslvoyUtil.setEtd(voyages);
                voyageInfo = getVoyageInfo(voyages);
            }
            voyageInfo.setSoId(soDo.getId());
            voyageInfo.setCntrList(getCntrBySoBooking(soDo.getId()));
            voyageInfo.setLoadingRequirements(getRequirementsBySoBooking(soDo.getId()));
        }
        return voyageInfo;
    }

    public VoyageRespVO getVoyageInfo(List<SpecialCargoVoyageUpdateReqVO> specialVoyages) {
        if (CollectionUtil.isEmpty(specialVoyages)) {
            throw ServiceExceptionUtil.exception(SoErrorCodeConstants.SO_NOT_EXISTS);
        }
        VoyageRespVO voyageRespVO = new VoyageRespVO();
        String polCode = StringUtils.EMPTY;
        String polEtd = StringUtils.EMPTY;
        String podCode = StringUtils.EMPTY;
        Set<String> statusSet = specialVoyages.stream().map(SpecialCargoVoyageUpdateReqVO::getRouteApproveStatus).collect(Collectors.toSet());
        SpecialCargoVoyageUpdateReqVO voyageDO = specialVoyages.stream().filter(voyage -> TransmodeEnum.LINER.getValue().equals(voyage.getTransmode())).findFirst().orElse(null);
        boolean inlandCargo = specialVoyages.stream().anyMatch(voyage -> !TransmodeEnum.LINER.getValue().equals(voyage.getTransmode()));
        if (null != voyageDO) {
            //当前这个polCode也可作为主信息表里存的currentPort
            polCode = voyageDO.getPolCode();
            polEtd = voyageDO.getPolEtd();
            podCode = voyageDO.getPodCode();
        }
        String porCode = specialVoyages.get(0).getPolCode();
        String delCode = specialVoyages.get(specialVoyages.size() - 1).getPodCode();
        voyageRespVO.setInlandCargo(inlandCargo);
        voyageRespVO.setTransitCnsha(applySaveOrUpdateService.updateTransitCnsha(polCode, podCode, specialVoyages));
        voyageRespVO.setPorCode(porCode);
        voyageRespVO.setPolCode(polCode);
        voyageRespVO.setPolEtd(polEtd);
        voyageRespVO.setPodCode(podCode);
        voyageRespVO.setDelCode(delCode);
        voyageRespVO.setApproveStatus(StringUtils.EMPTY);
        if (statusSet.contains(ApprovalStatusEnum.APPROVED.getValue())) {
            voyageRespVO.setApproveStatus(ApplyStatusEnum.APPROVED.getValue());
        }
        if (statusSet.contains(ApprovalStatusEnum.PENDING.getValue())) {
            voyageRespVO.setApproveStatus(ApplyStatusEnum.PENDING.getValue());
        }
        if (statusSet.contains(ApprovalStatusEnum.REJECT.getValue())) {
            voyageRespVO.setApproveStatus(ApplyStatusEnum.REJECT.getValue());
        }
        voyageRespVO.setVoyageList(specialVoyages);
        return voyageRespVO;
    }

    private List<SpecialCargoVoyageUpdateReqVO> getSpecialCargoVoyageDo(List<SoVslvoyDO> soVslvoyList) {
        List<SpecialCargoVoyageUpdateReqVO> specialVoyages = new ArrayList<>();
        boolean firstLiner = false;
        for (SoVslvoyDO soVslvoy : soVslvoyList) {
            SpecialCargoVoyageUpdateReqVO specialVoyage = new SpecialCargoVoyageUpdateReqVO();
            specialVoyage.setRouteNo(soVslvoy.getRouteNo());
            specialVoyage.setVesselCode(soVslvoy.getVesselCode());
            specialVoyage.setVoyageEx(soVslvoy.getVoyageEx());
            specialVoyage.setCarrier(soVslvoy.getVesselOperator());
            specialVoyage.setPolCode(soVslvoy.getPolCode());
            specialVoyage.setPodCode(soVslvoy.getPodCode());
            specialVoyage.setVoyageIm(soVslvoy.getVoyageIm());
            specialVoyage.setTransmode(soVslvoy.getTransmode());
            specialVoyage.setLaneCode(soVslvoy.getLaneCodeEx());
            specialVoyage.setRouteApproveStatus(ApprovalStatusEnum.PENDING.getValue());
            if (TransmodeEnum.LINER.getValue().equals(soVslvoy.getTransmode()) && !firstLiner) {
                firstLiner = true;
            }
            if (!firstLiner) {
                specialVoyage.setRouteApproveStatus(ApprovalStatusEnum.APPROVED.getValue());
            }
            specialVoyages.add(specialVoyage);
        }
        return specialVoyages;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(name = "createOrCommitSpc", keys = {"#createReqVo.creator", "#createReqVo.applyType"})
    public Long createOrCommitSpc(CreateOrCommitSpcReqVO createReqVo) {
        CommonUtil.validEmails(createReqVo.getApplyEmail());
        if (null != createReqVo.getPartnerFlag() && !createReqVo.getPartnerFlag() && StringUtils.isEmpty(createReqVo.getBookingNo())) {
            Assertion.assertTrue(false).raise0(SoErrorCodeConstants.SO_CHECK_NULL.getCode(), "bookingNo");
        }
        if (null != createReqVo.getSoId()) {
            SoDO soDO = soMapper.selectById(createReqVo.getSoId());
            if (SoStatusEnum.BookingRejected.getValue().equals(soDO.getSoStatus())) {
                throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.CREATE_SO_REJECT_ERROR);
            }
            if (SoStatusEnum.BookingCancelledByCustomer.getValue().equals(soDO.getSoStatus())) {
                throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.CREATE_SO_CANCEL_ERROR);
            }
        }
        String applyType = createReqVo.getApplyType();
        Long applyId;
        switch (SpecialEnum.valueOf(applyType)) {
            case ODO:
                OdoCreateOrUpdateVO odo = SpecialCargoApplyConvert.INSTANCE.convertReqOdo(createReqVo);
                applyId = spcExternalApiService.createOrUpdateOdo(odo);
                break;
            case FLEXI:
                List<SpecialCargoCntrBaseVO> cntrFlexiVos = createReqVo.getCntrAndFlexiInfos().stream()
                        .map(CntrAndFlexiBaseVO::getCntrBaseInfo).collect(Collectors.toList());
                checkCntr(SpecialEnum.FLEXI.getValue(), cntrFlexiVos);
                FlexiCreateOrUpdateVO flexi = SpecialCargoApplyConvert.INSTANCE.convertReqFlexi(createReqVo);
                applyId = spcExternalApiService.createOrUpdateFlexi(flexi);
                break;
            case DG:
                DgCreateOrUpdateVO dg = SpecialCargoApplyConvert.INSTANCE.convertReqDg(createReqVo);
                applyId = spcExternalApiService.createOrUpdateDg(dg);
                break;
            default:
                List<SpecialCargoCntrBaseVO> cntrAkVos = createReqVo.getCntrAndOogInfos().stream()
                        .map(CntrAndOogBaseVO::getCntrBaseInfo).collect(Collectors.toList());
                checkCntr(SpecialEnum.AK.getValue(), cntrAkVos);
                OogCreateOrUpdateVO oog = SpecialCargoApplyConvert.INSTANCE.convertReqOog(createReqVo);
                applyId = spcExternalApiService.createOrUpdateOog(oog);
        }
        if (createReqVo.getCommitOrSave()) {
            if (null != createReqVo.getSoId()) {
                SoDO soDO = soMapper.selectById(createReqVo.getSoId());
                if (SoStatusEnum.BookingDraft.getValue().equals(soDO.getSoStatus())) {
                    throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.APPLY_SO_DRAFT_ERROR);
                }
                if (SoStatusEnum.BookingRejected.getValue().equals(soDO.getSoStatus())) {
                    throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.APPLY_SO_REJECT_ERROR);
                }
                if (SoStatusEnum.BookingCancelledByCustomer.getValue().equals(soDO.getSoStatus())) {
                    throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.APPLY_SO_CANCEL_ERROR);
                }
            }
            createApproval(applyId, StringUtils.EMPTY, createReqVo.getChangeVoyCheck());
            if (CntrOwnerEnum.SNL.getValue().equals(createReqVo.getSpaceOwner())) {
                emailTaskService.createApplyEmailTask(applyId);
            }
        }
        return applyId;
    }

    public void checkCntr(String applyType, List<SpecialCargoCntrBaseVO> cntrBaseVos) {
        cntrBaseVos.forEach(cntr -> {
            if (SpecialEnum.AK.getValue().equals(applyType)) {
                List<String> cntrTypeAk = Arrays.asList("FR", "OT", "HC");
                if (!cntrTypeAk.contains(cntr.getCntrType())) {
                    throw ServiceExceptionUtil.exception(SpecialCargoCntrErrorCodeConstants.CNTR_TYPE_FOR_AK_ERROR);
                }
            }
            if (SpecialEnum.FLEXI.getValue().equals(applyType)) {
                List<String> cntrTypeFlexi = Arrays.asList("GP", "RF");
                if (CntrSizeEnum.size20.getValue().equals(cntr.getCntrSize()) && !cntrTypeFlexi.contains(cntr.getCntrType())) {
                    throw ServiceExceptionUtil.exception(SpecialCargoCntrErrorCodeConstants.CNTR_TYPE_FOR_FLEXI_ERROR);
                }
            }
        });
    }

    public String getApplyNo(String applyType) {
        int currentYear = Year.now().getValue();
        Long atomicLong = redisUtils.getAtomicLong(RedisKeyConstants.DOC_SPECIAL_APPLY_KEY + applyType + currentYear);
        return applyType + currentYear + String.format("%06d", atomicLong);
    }


    @Override
    public void deleteSpecialCargoApply(Long id) {
        // 删除
        SpecialCargoApplyDO applyDO = specialCargoApplyMapper.selectById(id);
        if (null == applyDO) {
            throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.SPECIAL_CARGO_APPLY_NOT_EXISTS);
        }
        if (!ApplyStatusEnum.EDIT.getValue().equals(applyDO.getApplyStatus())) {
            throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.SPECIAL_STATUS_DELETE_ERROR);
        }
        List<SpecialCargoCntrDO> cntrDOList = specialCargoCntrMapper.selectList(new LambdaQueryWrapper<SpecialCargoCntrDO>().eq(SpecialCargoCntrDO::getApplyId, id));
        String applyType = applyDO.getApplyType();
        List<Long> cntrIds = cntrDOList.stream().map(SpecialCargoCntrDO::getId).collect(Collectors.toList());
        if (SpecialEnum.DG.getValue().equals(applyType)) {
            List<SpecialCargoDgDO> dgDOList = specialCargoDgMapper.selectList(new LambdaQueryWrapper<SpecialCargoDgDO>().eq(SpecialCargoDgDO::getApplyId, applyDO.getId()));
            if (CollectionUtil.isNotEmpty(dgDOList)) {
                List<Long> dgIds = dgDOList.stream().map(SpecialCargoDgDO::getId).collect(Collectors.toList());
                LambdaQueryWrapper<SpecialCargoDgPackingDO> packWrapper = new LambdaQueryWrapper<>();
                packWrapper.in(SpecialCargoDgPackingDO::getDgId, dgIds);
                specialCargoDgPackingMapper.delete(packWrapper);
                LambdaQueryWrapper<SpecialCargoCasDO> casWrapper = new LambdaQueryWrapper<>();
                casWrapper.in(SpecialCargoCasDO::getSpecialCargoDgId, dgIds);
                specialCargoCasMapper.delete(casWrapper);
                specialCargoDgMapper.deleteBatchIds(dgIds);
            }
        }
        if (SpecialEnum.ODO.getValue().equals(applyType)) {
            LambdaQueryWrapper<SpecialCargoOdoDO> odoWrapper = new LambdaQueryWrapper<>();
            odoWrapper.in(SpecialCargoOdoDO::getCntrId, cntrIds);
            specialCargoOdoMapper.delete(odoWrapper);
        }
        if (SpecialEnum.OOG.getValue().equals(applyType) || SpecialEnum.AK.getValue().equals(applyType)) {
            LambdaQueryWrapper<SpecialCargoOogDO> oogWrapper = new LambdaQueryWrapper<>();
            oogWrapper.in(SpecialCargoOogDO::getCntrId, cntrIds);
            specialCargoOogMapper.delete(oogWrapper);

        }
        if (SpecialEnum.FLEXI.getValue().equals(applyType)) {
            LambdaQueryWrapper<SpecialCargoFlexiDO> flexiWrapper = new LambdaQueryWrapper<>();
            flexiWrapper.in(SpecialCargoFlexiDO::getCntrId, cntrIds);
            specialCargoFlexiMapper.delete(flexiWrapper);
        }
        specialCargoApplyMapper.deleteById(id);
        if (CollectionUtils.isNotEmpty(cntrIds)) {
            specialCargoCntrMapper.deleteBatchIds(cntrIds);
        }
        LambdaQueryWrapper<AttachmentDO> attachWrapper = new LambdaQueryWrapper<>();
        attachWrapper.eq(AttachmentDO::getReferenceId, id);
        attachmentMapper.delete(attachWrapper);
    }

    @Override
    public SpecialCargoApplyRespVO getSpecialCargoApplyInfo(Long id) {
        SpecialCargoApplyDO applyDo = specialCargoApplyMapper.selectById(id);
        SpecialCargoApplyRespVO convert = SpecialCargoApplyConvert.INSTANCE.convert(applyDo);
        List<SpecialCargoVoyageDO> voyageDos = specialCargoVoyageMapper.selectList(new LambdaQueryWrapper<SpecialCargoVoyageDO>().eq(SpecialCargoVoyageDO::getApplyId, id));
        if (CollectionUtil.isNotEmpty(voyageDos)) {
            List<SpecialCargoVoyageUpdateReqVO> voyages = SpecialCargoVoyageConvert.INSTANCE.convertList(voyageDos);
            costChargeUtil.setVslvoyTerminal(voyages);
            vslvoyUtil.setEtd(voyages);
            voyages.stream().filter(voyage -> TransmodeEnum.LINER.getValue().equals(voyage.getTransmode())).findFirst().ifPresent(voyageDo -> convert.setPolEtd(voyageDo.getPolEtd()));
            convert.setVoyageInfo(voyages);
        }
        List<SpecialCargoCntrDO> cntrDos = specialCargoCntrMapper.selectList(new LambdaQueryWrapper<SpecialCargoCntrDO>().eq(SpecialCargoCntrDO::getApplyId, id));
        String applyType = applyDo.getApplyType();
        if (CollectionUtil.isNotEmpty(cntrDos) && SpecialEnum.ODO.getValue().equals(applyType)) {
            List<Long> cntrIds = cntrDos.stream().map(SpecialCargoCntrDO::getId).collect(Collectors.toList());
            List<SpecialCargoOdoDO> odoList = specialCargoOdoMapper.selectList(new LambdaQueryWrapperX<SpecialCargoOdoDO>().in(SpecialCargoOdoDO::getCntrId, cntrIds));
            Map<Long, SpecialCargoOdoDO> cntrIdToOdoMap = odoList.stream().collect(Collectors.toMap(SpecialCargoOdoDO::getCntrId, e -> e));
            List<CntrAndOdoBaseVO> odoCntr = new ArrayList<>();
            for (SpecialCargoCntrDO cntr : cntrDos) {
                CntrAndOdoBaseVO cntrOdoBaseVO = new CntrAndOdoBaseVO();
                cntrOdoBaseVO.setCntrBaseInfo(SpecialCargoCntrConvert.INSTANCE.convert(cntr));
                SpecialCargoOdoDO odoDo = cntrIdToOdoMap.get(cntr.getId());
                if (null != odoDo) {
                    cntrOdoBaseVO.setOdoBaseInfo(SpecialCargoOdoConvert.INSTANCE.convert(odoDo));
                }
                odoCntr.add(cntrOdoBaseVO);
            }
            convert.setCntrAndOdoInfos(odoCntr);
        }
        if (CollectionUtil.isNotEmpty(cntrDos) && SpecialEnum.AK.getValue().equals(applyType)) {
            List<Long> cntrIds = cntrDos.stream().map(SpecialCargoCntrDO::getId).collect(Collectors.toList());
            List<SpecialCargoOogDO> oogList = specialCargoOogMapper.selectList(new LambdaQueryWrapperX<SpecialCargoOogDO>().in(SpecialCargoOogDO::getCntrId, cntrIds));
            Map<Long, SpecialCargoOogDO> cntrIdToOogMap = oogList.stream().collect(Collectors.toMap(SpecialCargoOogDO::getCntrId, e -> e));
            List<CntrAndOogBaseVO> oogCntr = new ArrayList<>();
            for (SpecialCargoCntrDO cntr : cntrDos) {
                CntrAndOogBaseVO cntrOogBaseVO = new CntrAndOogBaseVO();
                cntrOogBaseVO.setCntrBaseInfo(SpecialCargoCntrConvert.INSTANCE.convert(cntr));
                SpecialCargoOogDO oogDo = cntrIdToOogMap.get(cntr.getId());
                if (null != oogDo) {
                    cntrOogBaseVO.setOogBaseInfo(SpecialCargoOogConvert.INSTANCE.convert(oogDo));
                }
                oogCntr.add(cntrOogBaseVO);
            }
            convert.setCntrAndOogInfos(oogCntr);
        }
        if (CollectionUtil.isNotEmpty(cntrDos) && SpecialEnum.FLEXI.getValue().equals(applyType)) {
            List<Long> cntrIds = cntrDos.stream().map(SpecialCargoCntrDO::getId).collect(Collectors.toList());
            List<SpecialCargoFlexiDO> flexiList = specialCargoFlexiMapper.selectList(new LambdaQueryWrapperX<SpecialCargoFlexiDO>().in(SpecialCargoFlexiDO::getCntrId, cntrIds));
            Map<Long, SpecialCargoFlexiDO> cntrIdToFlexiMap = flexiList.stream().collect(Collectors.toMap(SpecialCargoFlexiDO::getCntrId, e -> e));
            List<CntrAndFlexiBaseVO> flexiCntr = new ArrayList<>();
            for (SpecialCargoCntrDO cntr : cntrDos) {
                CntrAndFlexiBaseVO cntrFlexiBaseVO = new CntrAndFlexiBaseVO();
                cntrFlexiBaseVO.setCntrBaseInfo(SpecialCargoCntrConvert.INSTANCE.convert(cntr));
                SpecialCargoFlexiDO flexiDo = cntrIdToFlexiMap.get(cntr.getId());
                if (null != flexiDo) {
                    cntrFlexiBaseVO.setFlexiBaseInfo(SpecialCargoFlexiConvert.INSTANCE.convert(flexiDo));
                }
                flexiCntr.add(cntrFlexiBaseVO);
            }
            convert.setCntrAndFlexiInfos(flexiCntr);
        }
        if (SpecialEnum.DG.getValue().equals(applyType)) {
            CntrAndDgBaseVO cntrAndDgBaseVO = new CntrAndDgBaseVO();
            List<SpecialCargoDgDO> dgDOList = specialCargoDgMapper.selectList(new LambdaQueryWrapper<SpecialCargoDgDO>().eq(SpecialCargoDgDO::getApplyId, applyDo.getId()));
            List<SpecialCargoDgBaseVO> specialCargoDgBaseVos = SpecialCargoDgConvert.INSTANCE.convertDo2BaseList(dgDOList);
            specialCargoDgBaseVos.forEach(dg -> {
                dg.setDgLabel(dg.getId());
                List<SpecialCargoDgExtDO> dgExtDOList = specialCargoDgExtMapper.selectList(new LambdaQueryWrapperX<SpecialCargoDgExtDO>().eq(SpecialCargoDgExtDO::getDgId, dg.getId()));
                if (CollectionUtil.isNotEmpty(dgExtDOList)) {
                    HashMap<String, String> dgExt = new HashMap<>(8);
                    for (SpecialCargoDgExtDO dgExtDo : dgExtDOList) {
                        dgExt.put(dgExtDo.getExtName(), dgExtDo.getExtValue());
                    }
                    dg.setDgExt(dgExt);
                }
                List<SpecialCargoDgPackingDO> packingDos = specialCargoDgPackingMapper.selectList(new LambdaQueryWrapper<SpecialCargoDgPackingDO>().eq(SpecialCargoDgPackingDO::getDgId, dg.getId()));
                List<PackingCreateOrUpdateVO> dgPackingInfo = SpecialCargoDgPackingConvert.INSTANCE.convertDo2PackVo(packingDos);
                Map<String, List<PackingCreateOrUpdateVO>> groupedByType = dgPackingInfo.stream()
                        .collect(Collectors.groupingBy(PackingCreateOrUpdateVO::getPackingType));
                dg.setInnerDgPacking(groupedByType.get(PackingEnum.INNER.getValue()));
                dg.setOuterDgPacking(groupedByType.get(PackingEnum.OUTER.getValue()));
                List<SpecialCargoCasDO> specialCargoCasDoList = specialCargoCasMapper.selectList(new LambdaQueryWrapper<SpecialCargoCasDO>().eq(SpecialCargoCasDO::getSpecialCargoDgId, dg.getId()));
                dg.setDgCasInfo(SpecialCargoCasConvert.INSTANCE.convertDo2CasVo(specialCargoCasDoList));
            });
            cntrAndDgBaseVO.setDgBaseInfo(specialCargoDgBaseVos);
            if (CollectionUtil.isNotEmpty(cntrDos)) {
                List<SpecialCargoCntrBaseVO> cntrBaseVOList = new ArrayList<>();
                for (SpecialCargoCntrDO cntr : cntrDos) {
                    SpecialCargoCntrBaseVO cntrBaseVO = SpecialCargoCntrConvert.INSTANCE.convertDoToBase(cntr);
                    List<CargoCntrTypeRelationDO> cntrDgRelations = cargoCntrTypeRelationMapper.selectList(new LambdaQueryWrapperX<CargoCntrTypeRelationDO>()
                            .eq(CargoCntrTypeRelationDO::getApplyId, id).eq(CargoCntrTypeRelationDO::getCntrId, cntr.getId()));
                    if (CollectionUtil.isNotEmpty(cntrDgRelations)) {
                        List<Long> dgIds = cntrDgRelations.stream().map(CargoCntrTypeRelationDO::getDgId).collect(Collectors.toList());
                        List<SpecialCargoDgDO> resultList = dgDOList.stream().filter(dgDO -> dgIds.contains(dgDO.getId())).collect(Collectors.toList());
                        List<String> unNo = resultList.stream().map(SpecialCargoDgDO::getUnNo).collect(Collectors.toList());
                        List<String> imoClass = resultList.stream().map(SpecialCargoDgDO::getImoClass).collect(Collectors.toList());
                        cntrBaseVO.setUnNo(unNo);
                        cntrBaseVO.setImoClass(imoClass);
                        cntrBaseVO.setDgId(dgIds);
                        cntrBaseVO.setDgLabels(dgIds);
                    }
                    cntrBaseVOList.add(cntrBaseVO);
                }
                cntrAndDgBaseVO.setCntrBaseInfo(cntrBaseVOList);
            }
            convert.setCntrAndDgInfos(cntrAndDgBaseVO);
        }
        convert.setSpcAttachmentList(attachmentService.getAttachmentListAll(id, applyType));
        return convert;
    }

    @Override
    public SpecialCargoApplyRespVO copySpc(SpecialApplyCommonReqVO originalVo) {
        SpecialCargoApplyDO applyDO = specialCargoApplyMapper.selectById(originalVo.getOriginalId());
        SpecialCargoApplyRespVO newApply = SpecialCargoApplyConvert.INSTANCE.convert(applyDO);
        String applyType = applyDO.getApplyType();
        newApply.setId(null);
        newApply.setApplyStatus(ApplyStatusEnum.EDIT.getValue());
        newApply.setApplyNo(getApplyNo(applyType));
        newApply.setBookingNo(StringUtils.EMPTY);
        List<SpecialCargoCntrDO> cntrDos = specialCargoCntrMapper.selectList(new LambdaQueryWrapper<SpecialCargoCntrDO>().eq(SpecialCargoCntrDO::getApplyId, originalVo.getOriginalId()));
        if (CollectionUtil.isNotEmpty(cntrDos) && SpecialEnum.ODO.getValue().equals(applyType)) {
            List<CntrAndOdoBaseVO> odoCntr = new ArrayList<>();
            for (SpecialCargoCntrDO cntr : cntrDos) {
                CntrAndOdoBaseVO cntrOdoBaseVO = new CntrAndOdoBaseVO();
                SpecialCargoCntrRespVO cntrBase = SpecialCargoCntrConvert.INSTANCE.convert(cntr);
                cntrBase.setId(null);
                cntrBase.setApplyId(null);
                cntrOdoBaseVO.setCntrBaseInfo(cntrBase);
                SpecialCargoOdoDO odoDO = specialCargoOdoMapper.selectOne(new LambdaQueryWrapperX<SpecialCargoOdoDO>().eq(SpecialCargoOdoDO::getCntrId, cntr.getId()));
                if (null != odoDO) {
                    SpecialCargoOdoRespVO odoBase = SpecialCargoOdoConvert.INSTANCE.convert(odoDO);
                    odoBase.setId(null);
                    cntrOdoBaseVO.setOdoBaseInfo(odoBase);
                }
                odoCntr.add(cntrOdoBaseVO);
            }
            newApply.setCntrAndOdoInfos(odoCntr);
        }
        if (CollectionUtil.isNotEmpty(cntrDos) && SpecialEnum.AK.getValue().equals(applyType)) {
            List<CntrAndOogBaseVO> oogCntr = new ArrayList<>();
            for (SpecialCargoCntrDO cntr : cntrDos) {
                CntrAndOogBaseVO cntrOogBaseVO = new CntrAndOogBaseVO();
                SpecialCargoCntrRespVO cntrBase = SpecialCargoCntrConvert.INSTANCE.convert(cntr);
                cntrBase.setId(null);
                cntrBase.setApplyId(null);
                cntrOogBaseVO.setCntrBaseInfo(cntrBase);
                SpecialCargoOogDO oogDO = specialCargoOogMapper.selectOne(new LambdaQueryWrapperX<SpecialCargoOogDO>().eq(SpecialCargoOogDO::getCntrId, cntr.getId()));
                if (null != oogDO) {
                    SpecialCargoOogRespVO oogBase = SpecialCargoOogConvert.INSTANCE.convert(oogDO);
                    oogBase.setId(null);
                    cntrOogBaseVO.setOogBaseInfo(oogBase);
                }
                oogCntr.add(cntrOogBaseVO);
            }
            newApply.setCntrAndOogInfos(oogCntr);
        }
        if (CollectionUtil.isNotEmpty(cntrDos) && SpecialEnum.FLEXI.getValue().equals(applyType)) {
            List<CntrAndFlexiBaseVO> flexiCntr = new ArrayList<>();
            for (SpecialCargoCntrDO cntr : cntrDos) {
                CntrAndFlexiBaseVO cntrFlexiBaseVO = new CntrAndFlexiBaseVO();
                SpecialCargoCntrRespVO cntrBase = SpecialCargoCntrConvert.INSTANCE.convert(cntr);
                cntrBase.setId(null);
                cntrBase.setApplyId(null);
                cntrFlexiBaseVO.setCntrBaseInfo(cntrBase);
                SpecialCargoFlexiDO flexiDO = specialCargoFlexiMapper.selectOne(new LambdaQueryWrapperX<SpecialCargoFlexiDO>().eq(SpecialCargoFlexiDO::getCntrId, cntr.getId()));
                if (null != flexiDO) {
                    SpecialCargoFlexiRespVO flexiBase = SpecialCargoFlexiConvert.INSTANCE.convert(flexiDO);
                    flexiBase.setId(null);
                    cntrFlexiBaseVO.setFlexiBaseInfo(flexiBase);
                }
                flexiCntr.add(cntrFlexiBaseVO);
            }
            newApply.setCntrAndFlexiInfos(flexiCntr);
        }
        if (SpecialEnum.DG.getValue().equals(applyType)) {
            CntrAndDgBaseVO cntrAndDgBaseVO = new CntrAndDgBaseVO();
            List<SpecialCargoDgDO> dgDOList = specialCargoDgMapper.selectList(new LambdaQueryWrapper<SpecialCargoDgDO>().eq(SpecialCargoDgDO::getApplyId, applyDO.getId()));
            List<SpecialCargoDgBaseVO> dgBase = SpecialCargoDgConvert.INSTANCE.convertDo2BaseList(dgDOList);
            for (int i = 0; i < dgBase.size(); i++) {
                SpecialCargoDgBaseVO dg = dgBase.get(i);
                dg.setDgLabel((long) i);
                dg.setApplyId(null);
                List<SpecialCargoDgPackingDO> packingDos = specialCargoDgPackingMapper.selectList(new LambdaQueryWrapper<SpecialCargoDgPackingDO>().eq(SpecialCargoDgPackingDO::getDgId, dg.getId()));
                List<PackingCreateOrUpdateVO> dgPackingInfo = SpecialCargoDgPackingConvert.INSTANCE.convertDo2PackVo(packingDos);
                dgPackingInfo.forEach(pack -> pack.setId(null));
                Map<String, List<PackingCreateOrUpdateVO>> groupedByType = dgPackingInfo.stream()
                        .collect(Collectors.groupingBy(PackingCreateOrUpdateVO::getPackingType));
                dg.setInnerDgPacking(groupedByType.get(PackingEnum.INNER.getValue()));
                dg.setOuterDgPacking(groupedByType.get(PackingEnum.OUTER.getValue()));
                List<SpecialCargoCasDO> specialCargoCasDoList = specialCargoCasMapper.selectList(new LambdaQueryWrapper<SpecialCargoCasDO>().eq(SpecialCargoCasDO::getSpecialCargoDgId, dg.getId()));
                specialCargoCasDoList.forEach(cas -> cas.setId(null));
                dg.setDgCasInfo(SpecialCargoCasConvert.INSTANCE.convertDo2CasVo(specialCargoCasDoList));
                List<SpecialCargoDgExtDO> dgExtDOList = specialCargoDgExtMapper.selectList(new LambdaQueryWrapperX<SpecialCargoDgExtDO>().eq(SpecialCargoDgExtDO::getDgId, dg.getId()));
                if (CollectionUtil.isNotEmpty(dgExtDOList)) {
                    HashMap<String, String> dgExt = new HashMap<>(8);
                    for (SpecialCargoDgExtDO dgExtDo : dgExtDOList) {
                        dgExt.put(dgExtDo.getExtName(), dgExtDo.getExtValue());
                    }
                    dg.setDgExt(dgExt);
                }
                dg.setId(null);
            }
            cntrAndDgBaseVO.setDgBaseInfo(dgBase);
            if (CollectionUtil.isNotEmpty(cntrDos)) {
                List<SpecialCargoCntrBaseVO> cntrBaseVOList = new ArrayList<>();
                for (SpecialCargoCntrDO cntr : cntrDos) {
                    SpecialCargoCntrBaseVO cntrBaseVO = SpecialCargoCntrConvert.INSTANCE.convertDoToBase(cntr);
                    cntrBaseVO.setId(null);
                    cntrBaseVO.setApplyId(null);
                    cntrBaseVOList.add(cntrBaseVO);
                }
                cntrAndDgBaseVO.setCntrBaseInfo(cntrBaseVOList);
            }
            newApply.setCntrAndDgInfos(cntrAndDgBaseVO);
        }
        List<AttachmentRespVO> attachmentListAll = attachmentService.getAttachmentListAll(originalVo.getOriginalId(), applyType);
        attachmentListAll.forEach(attach -> {
            attach.setId(null);
            attach.setReferenceId(null);
        });
        newApply.setSpcAttachmentList(attachmentListAll);
        return newApply;
    }

    @Override
    public PageResult<ApplyAndVoyageInfoVO> getSpecialCargoApplyPage(SpecialCargoApplyPageReqVO reqVo) {
        if (reqVo.getEndDate() != null) {
            // 默认结束时间包括当天
            reqVo.setEndDate(reqVo.getEndDate().plusDays(1));
        }
        IPage<ApplyAndVoyageInfoVO> page = Page.of(reqVo.getPageNo(), reqVo.getPageSize());
        IPage<ApplyAndVoyageInfoVO> pageResult = specialCargoApplyMapper.getApplyAndVoyageInfo(page, reqVo);
        List<ApplyAndVoyageInfoVO> records = pageResult.getRecords();
        if (CollectionUtil.isEmpty(records)) {
            return new PageResult<>(records, pageResult.getTotal());
        }
        List<Long> applyIds = records.stream().map(ApplyAndVoyageInfoVO::getId).distinct().collect(Collectors.toList());
        List<SpecialCargoVoyageDO> voyageDos = specialCargoVoyageMapper.selectList(new LambdaQueryWrapper<SpecialCargoVoyageDO>()
                .eq(SpecialCargoVoyageDO::getTransmode, TransmodeEnum.LINER.getValue())
                .in(SpecialCargoVoyageDO::getApplyId, applyIds));
        Map<Long, SpecialCargoVoyageDO> resultMap = new HashMap<>();
        for (SpecialCargoVoyageDO voyage : voyageDos) {
            Long applyId = voyage.getApplyId();
            if (!resultMap.containsKey(applyId)) {
                resultMap.put(applyId, voyage);
            } else {
                SpecialCargoVoyageDO existing = resultMap.get(applyId);
                if (voyage.getRouteNo() < existing.getRouteNo()) {
                    resultMap.put(applyId, voyage);
                }
            }
        }
        Map<Long, Integer> attachmentSumMap = new HashMap<>(16);
        List<AttachmentCountVO> attachmentCountVos = attachmentService.selectSumByReferenceIds(applyIds, SoSiEnum.SPC.getValue());
        if (CollectionUtil.isNotEmpty(attachmentCountVos)) {
            attachmentSumMap = attachmentCountVos.stream().collect(Collectors.toMap(AttachmentCountVO::getReferenceId, AttachmentCountVO::getAttachmentCount));
        }
        Map<Long, Integer> finalAttachmentSumMap = attachmentSumMap;
        records.forEach(applyInfo -> {
            String approver = applyInfo.getApprover();
            if(StringUtils.isNotEmpty(approver)){
                List<ApplyApproverVO> approvers = new ArrayList<>();
                String[] split = approver.split(",");
                for (String item : split) {
                    if (item != null && !item.isEmpty()) {
                        ApplyApproverVO applyApprover = new ApplyApproverVO();
                        String[] parts = item.split(":");
                        applyApprover.setRouteNo(Integer.valueOf(parts[0]));
                        applyApprover.setApprover(parts[1]);
                        approvers.add(applyApprover);
                    }
                }
                applyInfo.setApprovers(approvers);
            }
            Integer attachmentNum = finalAttachmentSumMap.get(applyInfo.getId()) == null ? 0 : finalAttachmentSumMap.get(applyInfo.getId());
            applyInfo.setAttach(attachmentNum);
            applyInfo.setPolEtd(StringUtils.EMPTY);
            SpecialCargoVoyageDO voyage = resultMap.get(applyInfo.getId());
            if (null != voyage) {
                applyInfo.setPolEtd(vslvoyUtil.getEtd(voyage.getPolCode(), voyage.getVoyageEx(), voyage.getVesselCode()));
            }
        });

        return new PageResult<>(records, pageResult.getTotal());
    }

    @Override
    public List<ApplyAndVoyageInfoExportVO> getExportList(SpecialCargoApplyPageReqVO reqVO) {
        if (reqVO.getEndDate() != null) {
            // 默认结束时间包括当天
            reqVO.setEndDate(reqVO.getEndDate().plusDays(1));
        }
        List<ApplyAndVoyageInfoVO> records = specialCargoApplyMapper.getApplyAndVoyageInfo(reqVO);
        if (CollectionUtil.isEmpty(records)) {
            return new ArrayList<>();
        }
        vslvoyUtil.setEtdPage(records);
        List<Long> applyIds = records.stream().map(ApplyAndVoyageInfoVO::getId).distinct().collect(Collectors.toList());
        List<SpecialCargoVoyageDO> voyageDos = specialCargoVoyageMapper.selectList(new LambdaQueryWrapper<SpecialCargoVoyageDO>()
                .eq(SpecialCargoVoyageDO::getTransmode, TransmodeEnum.LINER.getValue())
                .in(SpecialCargoVoyageDO::getApplyId, applyIds));
        Map<Long, SpecialCargoVoyageDO> resultMap = new HashMap<>();
        for (SpecialCargoVoyageDO voyage : voyageDos) {
            Long applyId = voyage.getApplyId();
            if (!resultMap.containsKey(applyId)) {
                resultMap.put(applyId, voyage);
            } else {
                SpecialCargoVoyageDO existing = resultMap.get(applyId);
                if (voyage.getRouteNo() < existing.getRouteNo()) {
                    resultMap.put(applyId, voyage);
                }
            }
        }
        Map<Long, Integer> attachmentSumMap = new HashMap<>(16);
        List<AttachmentCountVO> attachmentCountVos = attachmentService.selectSumByReferenceIds(applyIds, SoSiEnum.SPC.getValue());
        if (CollectionUtil.isNotEmpty(attachmentCountVos)) {
            attachmentSumMap = attachmentCountVos.stream().collect(Collectors.toMap(AttachmentCountVO::getReferenceId, AttachmentCountVO::getAttachmentCount));
        }
        Map<Long, Integer> finalAttachmentSumMap = attachmentSumMap;
        records.forEach(applyInfo -> {
            String approver = applyInfo.getApprover();
            if(StringUtils.isNotEmpty(approver)){
                List<ApplyApproverVO> approvers = new ArrayList<>();
                String[] split = approver.split(",");
                for (String item : split) {
                    if (item != null && !item.isEmpty()) {
                        ApplyApproverVO applyApprover = new ApplyApproverVO();
                        String[] parts = item.split(":");
                        applyApprover.setRouteNo(Integer.valueOf(parts[0]));
                        applyApprover.setApprover(parts[1]);
                        approvers.add(applyApprover);
                    }
                }
                applyInfo.setApprovers(approvers);
            }
            Integer attachmentNum = finalAttachmentSumMap.get(applyInfo.getId()) == null ? 0 : finalAttachmentSumMap.get(applyInfo.getId());
            applyInfo.setAttach(attachmentNum);
            applyInfo.setPolEtd(StringUtils.EMPTY);
            SpecialCargoVoyageDO voyage = resultMap.get(applyInfo.getId());
            if (null != voyage) {
                applyInfo.setPolEtd(vslvoyUtil.getEtd(voyage.getPolCode(), voyage.getVoyageEx(), voyage.getVesselCode()));
            }
        });
        return BeanUtil.copyToList(records, ApplyAndVoyageInfoExportVO.class);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createApproval(Long applyId, String applyUser, boolean changeVoyCheck) {
        SpecialCargoApplyDO applyDO = specialCargoApplyMapper.selectById(applyId);
        if (SoDataSourceEnum.MANUAL.getValue().equals(applyDO.getDataSource())) {
            applyDO.setApplyUser(String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
        } else {
            applyDO.setApplyUser(applyUser);
        }
        String applyStatus = applyDO.getApplyStatus();
        if (ApplyStatusEnum.REVOKE.getValue().equals(applyStatus)) {
            throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.SPECIAL_APPLY_STATUS_ERROR);
        }
        List<AttachmentTypeDO> attachmentTypeDOList = attachmentTypeMapper.selectList(new LambdaQueryWrapperX<AttachmentTypeDO>()
                .eq(AttachmentTypeDO::getUseModules, SpecialEnum.SPC.getValue())
                .eqIfPresent(AttachmentTypeDO::getUseFunctions, applyDO.getApplyType())
                .eq(AttachmentTypeDO::getRequiredStatus, true));
        List<AttachmentDO> attachmentDos = attachmentMapper.selectList(new LambdaQueryWrapperX<AttachmentDO>()
                .eq(AttachmentDO::getReferenceId, applyId)
                .eq(AttachmentDO::getReferenceType, SpecialEnum.SPC.getValue())
                .isNotNull(AttachmentDO::getFileId));
        List<String> attachmentTypeCodes = attachmentTypeDOList.stream().map(AttachmentTypeDO::getAttatchTypeCode).collect(Collectors.toList());
        List<String> attachmentCodes = attachmentDos.stream().map(AttachmentDO::getAttachTypeCode).distinct().collect(Collectors.toList());
        List<String> attachmentMissing = attachmentTypeCodes.stream().filter(code -> !attachmentCodes.contains(code)).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(attachmentMissing)) {
            throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.APPLY_ATTACHMENT_MISSING);
        }
        // 获取特殊申请类型
        SpecialEnum specialEnum = SpecialEnum.getByValue(applyDO.getApplyType());
        if (specialEnum == null) {
            throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.SPECIAL_APPLY_TYPE_ERROR);
        }
        // 获取特殊申请类型对应的校验器
        SpecialCargoValidateService validatorByType = SpecialValidatorEnum.getValidatorByType(specialEnum);
        if (validatorByType == null) {
            throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.SPECIAL_APPLY_TYPE_ERROR);
        }
        List<SpecialCargoCntrDO> cntrDOList = specialCargoCntrMapper.selectList(new LambdaQueryWrapper<SpecialCargoCntrDO>().eq(SpecialCargoCntrDO::getApplyId, applyId));
        List<SpecialCargoVoyageDO> specialCargoVoyageDos = specialCargoVoyageMapper.selectList(new LambdaQueryWrapper<SpecialCargoVoyageDO>().eq(SpecialCargoVoyageDO::getApplyId, applyId));
        validatorByType.validate(applyId, applyDO, cntrDOList, specialCargoVoyageDos);
        specialCargoVoyageDos.forEach(voyage -> {
            String vesselCode = voyage.getVesselCode();
            VesselOffHireMaintenanceDO vesselOffHireMaintenanceDO = vesselOffHireMaintenanceMapper.selectOne(new LambdaQueryWrapper<VesselOffHireMaintenanceDO>().eq(VesselOffHireMaintenanceDO::getVesselCode, vesselCode));
            if (null != vesselOffHireMaintenanceDO) {
                if (CommonUtil.isIsBetween(vesselOffHireMaintenanceDO.getBeginDate(), vesselOffHireMaintenanceDO.getEndDate())) {
                    throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.SPECIAL_APPLY_VESSEL_OFF_HIRE);
                }
            }
            //出口的内直线的是自动审批通过
            if (!ApprovalStatusEnum.APPROVED.getValue().equals(voyage.getRouteApproveStatus()) && !changeVoyCheck) {
                voyage.setRouteApproveStatus(ApprovalStatusEnum.PENDING.getValue());
                specialCargoVoyageMapper.updateById(voyage);
            }
        });
        if (changeVoyCheck) {
            applyDO.setApplyStatus(ApplyStatusEnum.CHANGEVOYCHECK.getValue());
        } else {
            applyDO.setApplyStatus(ApplyStatusEnum.PENDING.getValue());
        }

        applyDO.setApplyTime(new Date());
        specialCargoApplyMapper.updateById(applyDO);
    }

    @Override
    public List<String> getBookingNo() {
        LambdaQueryWrapperX<SoDO> queryWrapperX = new LambdaQueryWrapperX<SoDO>().orderByDesc(SoDO::getCreateTime);
        List<SoDO> soDOList = soMapper.selectList(queryWrapperX);
        return soDOList.stream().map(SoDO::getBookingNo).filter(bookingNo -> bookingNo != null && !bookingNo.trim().isEmpty()).distinct().collect(Collectors.toList());
    }

    @Override
    public void cancelApply(Long applyId, boolean backEdit) {
        SpecialCargoApplyDO entity = specialCargoApplyMapper.selectById(applyId);
        String applyStatus = entity.getApplyStatus();
        if (backEdit) {
            if (!ApplyStatusEnum.PENDING.getValue().equals(applyStatus)) {
                throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.SPECIAL_APPLY_RETURN_ERROR);
            }
            entity.setApplyStatus(ApplyStatusEnum.EDIT.getValue());
        } else {
            if (ApplyStatusEnum.EDIT.getValue().equals(applyStatus)
                    || ApplyStatusEnum.APPROVED.getValue().equals(applyStatus)
                    || ApplyStatusEnum.REJECT.getValue().equals(applyStatus)) {
                throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.SPECIAL_APPLY_CANCEL_ERROR);
            }
            if (ApplyStatusEnum.APPLY_CANCELLED.getValue().equals(applyStatus)) {
                throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.SPECIAL_APPLY_CANCEL);
            }
            entity.setApplyStatus(ApplyStatusEnum.APPLY_CANCELLED.getValue());
        }
        specialCargoApplyMapper.updateById(entity);
    }

    @Override
    public void returnEdit(List<Long> applyIds) {
        LambdaUpdateWrapper<SpecialCargoApplyDO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.in(SpecialCargoApplyDO::getId, applyIds);
        updateWrapper.set(SpecialCargoApplyDO::getApplyStatus, ApplyStatusEnum.EDIT.getValue());
        updateWrapper.set(SpecialCargoApplyDO::getUpdater, String.valueOf(SecurityFrameworkUtils.getLoginUserId()));
        updateWrapper.set(SpecialCargoApplyDO::getUpdateTime, LocalDateTime.now());
        specialCargoApplyMapper.update(null, updateWrapper);
    }

    @Override
    public String newApply(SpecialApplyCommonReqVO req) {
        String applyType = req.getApplyType();
        return getApplyNo(applyType);
    }

    @Override
    public String perCheck(ApplyAndVoyageInfoVO req) {
        List<SpecialCargoCntrDO> cntrDOList = specialCargoCntrMapper.selectList(new LambdaQueryWrapper<SpecialCargoCntrDO>().eq(SpecialCargoCntrDO::getApplyId, req.getId()));
        if (CollectionUtil.isEmpty(cntrDOList)) {
            throw ServiceExceptionUtil.exception(SpecialCargoApplyErrorCodeConstants.SPECIAL_CARGO_CNTR_NOT_EXISTS);
        }
        for (SpecialCargoCntrDO cntr : cntrDOList) {
            LambdaQueryWrapperX<ContainerMaxPayloadDO> queryWrapperX = new LambdaQueryWrapperX<>();
            queryWrapperX.eqIfPresent(ContainerMaxPayloadDO::getCntrType, cntr.getCntrType())
                    .eqIfPresent(ContainerMaxPayloadDO::getCntrSize, cntr.getCntrSize());
            ContainerMaxPayloadDO payloadDO = containerMaxPayloadMapper.selectOne(queryWrapperX);
            if (null != payloadDO) {
                Integer maxPayload = payloadDO.getMaxPayload();
                int result = cntr.getNetWgt().compareTo(BigDecimal.valueOf(maxPayload));
                if (result > 0) {
                    return cntr.getCntrSize() + cntr.getCntrType() + "箱型超过最大载重" + maxPayload;
                }
            }
        }
        return StringUtils.EMPTY;
    }

    @Override
    public List<SpecialCargoApplyDO> getByBookingNos(List<String> bookingNos) {
        if (CollectionUtil.isEmpty(bookingNos)) {
            return Collections.emptyList();
        }
        LambdaQueryWrapperX<SpecialCargoApplyDO> queryWrapperX =
                new LambdaQueryWrapperX<SpecialCargoApplyDO>().in(SpecialCargoApplyDO::getBookingNo, bookingNos);
        return specialCargoApplyMapper.selectList(queryWrapperX);
    }

    @Override
    public SpecialCargoApplyDO getByBookingNo(String bookingNo) {
        LambdaQueryWrapperX<SpecialCargoApplyDO> queryWrapperX = new LambdaQueryWrapperX<SpecialCargoApplyDO>()
                .eq(SpecialCargoApplyDO::getBookingNo, bookingNo).last("LIMIT 1");
        return specialCargoApplyMapper.selectOne(queryWrapperX);
    }

    /**
     * 特种货申请-特种货获取SO信息，同步部分字段值
     *
     * @param vslvoyListMap map
     */
    @Override
    public void updateVslvoy(Map<String, SoVslvoyRespVO> vslvoyListMap) {
        for (String bookingNo : vslvoyListMap.keySet()) {
            SpecialCargoApplyDO applyDO = getByBookingNo(bookingNo);
            if (null != applyDO && !ApplyStatusEnum.APPROVED.getValue().equals(applyDO.getApplyStatus())
                    && !ApplyStatusEnum.REJECT.getValue().equals(applyDO.getApplyStatus())) {
                SpecialCargoVoyageDO voyageDO = specialCargoVoyageMapper.selectOne(new LambdaQueryWrapper<SpecialCargoVoyageDO>()
                        .eq(SpecialCargoVoyageDO::getApplyId, applyDO.getId()).eq(SpecialCargoVoyageDO::getTransmode, TransmodeEnum.LINER.getValue())
                        .orderByAsc(SpecialCargoVoyageDO::getRouteNo).last("LIMIT 1"));
                if (null != voyageDO) {
                    SoVslvoyRespVO soVslvoyRespVO = vslvoyListMap.get(bookingNo);
                    voyageDO.setVesselCode(soVslvoyRespVO.getVesselCode());
                    voyageDO.setVoyageEx(soVslvoyRespVO.getVoyageEx());
                    specialCargoVoyageMapper.updateById(voyageDO);
                }
            }
        }
    }

    @Override
    public List<DgByBookingNo> getDgByBookingNo(String bookingNo) {
        List<DgByBookingNo> dgInfos = new ArrayList<>();
        LambdaQueryWrapperX<SpecialCargoApplyDO> queryWrapperX = new LambdaQueryWrapperX<SpecialCargoApplyDO>()
                .eq(SpecialCargoApplyDO::getBookingNo, bookingNo).eq(SpecialCargoApplyDO::getApplyType, SpecialEnum.DG.getValue()).last("LIMIT 1");
        SpecialCargoApplyDO applyDO = specialCargoApplyMapper.selectOne(queryWrapperX);
        if (null != applyDO) {
            List<SpecialCargoDgDO> dgDos = specialCargoDgMapper.selectList(new LambdaQueryWrapper<SpecialCargoDgDO>().eq(SpecialCargoDgDO::getApplyId, applyDO.getId()));
            if (CollectionUtil.isNotEmpty(dgDos)) {
                dgDos.forEach(dg -> {
                    DgByBookingNo dgInfo = new DgByBookingNo();
                    dgInfo.setUnNo(dg.getUnNo());
                    dgInfo.setImoClass(dg.getImoClass());
                    dgInfo.setFlashPoint(dg.getFlashPoint());
                    dgInfo.setPackingGroup(dg.getPackingGroup());
                    dgInfos.add(dgInfo);
                });
            }
        }
        return dgInfos;
    }

    @Override
    public boolean directTruckPod(String delCode) {
        CommonResult<List<DictDataRespDTO>> dictDataList = dictDataApi.getDictDataList("doc_sc_port_of_direct");
        if (dictDataList != null && dictDataList.getData() != null) {
            Optional<DictDataRespDTO> matchedDict = dictDataList.getData().stream()
                    .filter(dict -> delCode.equals(dict.getValue()) && dict.getStatus() == DICT_STATUS)
                    .findFirst();
            return matchedDict.isPresent();
        }
        return false;
    }
}
